/** @file

  Copyright (c) 2004  - 2014, Intel Corporation. All rights reserved.<BR>
                                                                                   
  SPDX-License-Identifier: BSD-2-Clause-Patent

                                                                                   

Module Name:


  Recovery.c

Abstract:

  Tiano PEIM to provide the platform recovery functionality.

--*/

#include "PlatformEarlyInit.h"

#define PEI_FVMAIN_COMPACT_GUID \
  {0x4A538818, 0x5AE0, 0x4eb2, 0xB2, 0xEB, 0x48, 0x8b, 0x23, 0x65, 0x70, 0x22};

EFI_GUID FvMainCompactFileGuid = PEI_FVMAIN_COMPACT_GUID;

//
// Required Service
//
EFI_STATUS
EFIAPI
PlatformRecoveryModule (
  IN CONST EFI_PEI_SERVICES                       **PeiServices,
  IN EFI_PEI_RECOVERY_MODULE_PPI          *This
  );

//
// Module globals
//

typedef struct {
  EFI_GUID  CapsuleGuid;
  UINT32    HeaderSize;
  UINT32    Flags;
  UINT32    CapsuleImageSize;
  UINT32    SequenceNumber;
  EFI_GUID  InstanceId;
  UINT32    OffsetToSplitInformation;
  UINT32    OffsetToCapsuleBody;
  UINT32    OffsetToOemDefinedHeader;
  UINT32    OffsetToAuthorInformation;
  UINT32    OffsetToRevisionInformation;
  UINT32    OffsetToShortDescription;
  UINT32    OffsetToLongDescription;
  UINT32    OffsetToApplicableDevices;
} OLD_EFI_CAPSULE_HEADER;


static EFI_PEI_RECOVERY_MODULE_PPI mRecoveryPpi = {
  PlatformRecoveryModule
};

static EFI_PEI_PPI_DESCRIPTOR mRecoveryPpiList = {
  (EFI_PEI_PPI_DESCRIPTOR_PPI | EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST),
  &gEfiPeiRecoveryModulePpiGuid,
  &mRecoveryPpi
};

/**
  Provide the functionality of the Recovery Module.

  @param PeiServices  General purpose services available to every PEIM.

  @retval Status      EFI_SUCCESS if the interface could be successfully
                      installed

**/
EFI_STATUS
EFIAPI
PeimInitializeRecovery (
  IN CONST EFI_PEI_SERVICES     **PeiServices
  )
{
  EFI_STATUS  Status;

  Status = (*PeiServices)->InstallPpi (
                             PeiServices,
                             &mRecoveryPpiList
                             );

  return Status;
}

/**
  Provide the functionality of the Ea Recovery Module.

  @param PeiServices         General purpose services available to every PEIM.
  @param This                Pointer to PEI_RECOVERY_MODULE_INTERFACE.

  @retval EFI_SUCCESS        If the interface could be successfully
                             installed.
  @retval EFI_UNSUPPORTED    Not supported.

**/
EFI_STATUS
EFIAPI
PlatformRecoveryModule (
  IN CONST EFI_PEI_SERVICES               **PeiServices,
  IN EFI_PEI_RECOVERY_MODULE_PPI          *This
  )
{
  EFI_STATUS                            Status;
  EFI_PEI_DEVICE_RECOVERY_MODULE_PPI    *DeviceRecoveryModule;
  UINTN                                 NumberOfImageProviders;
  BOOLEAN                               ProviderAvailable;
  UINTN                                 NumberRecoveryCapsules;
  UINTN                                 RecoveryCapsuleSize;
  EFI_GUID                              DeviceId;
  BOOLEAN                               ImageFound;
  EFI_PHYSICAL_ADDRESS                  Address;
  VOID                                  *Buffer;
  OLD_EFI_CAPSULE_HEADER                *CapsuleHeader;
  EFI_PEI_HOB_POINTERS                  Hob;
  EFI_PEI_HOB_POINTERS                  HobOld;
  EFI_HOB_CAPSULE_VOLUME                *CapsuleHob;
  BOOLEAN                               HobUpdate;
  EFI_FIRMWARE_VOLUME_HEADER            *FvHeader;
  UINTN                                 Index;
  BOOLEAN                                FoundFvMain;
  BOOLEAN                                FoundCapsule;
  static EFI_GUID mEfiCapsuleHeaderGuid = EFI_CAPSULE_GUID;
  EFI_PEI_STALL_PPI                      *StallPpi;

  (*PeiServices)->ReportStatusCode (
                    PeiServices,
                    EFI_PROGRESS_CODE,
                    EFI_SOFTWARE_PEI_MODULE | EFI_SW_PEIM_PC_RECOVERY_BEGIN,
                    0,
                    NULL,
                    NULL
                    );

  Status = (**PeiServices).LocatePpi (
              PeiServices,
              &gEfiPeiStallPpiGuid,
              0,
              NULL,
              &StallPpi
              );
  ASSERT_EFI_ERROR (Status);

  StallPpi->Stall(
              PeiServices,
              StallPpi,
              5000000
              );


  Index = 0;

  Status                  = EFI_SUCCESS;
  HobUpdate               = FALSE;

  ProviderAvailable       = TRUE;
  ImageFound              = FALSE;
  NumberOfImageProviders  = 0;

  DeviceRecoveryModule    = NULL;

  FoundCapsule = FALSE;
  FoundFvMain = FALSE;

  DEBUG ((EFI_D_ERROR | EFI_D_LOAD, "Recovery Entry\n"));

  //
  // Search the platform for some recovery capsule if the DXE IPL
  // discovered a recovery condition and has requested a load.
  //
  while (ProviderAvailable == TRUE) {

    Status = (*PeiServices)->LocatePpi (
                               PeiServices,
                               &gEfiPeiDeviceRecoveryModulePpiGuid,
                               Index,
                               NULL,
                               &DeviceRecoveryModule
                               );

    if (!EFI_ERROR (Status)) {
      DEBUG ((EFI_D_INFO | EFI_D_LOAD, "Device Recovery PPI located\n"));
      NumberOfImageProviders++;

      Status = DeviceRecoveryModule->GetNumberRecoveryCapsules (
                                       (EFI_PEI_SERVICES**)PeiServices,
                                       DeviceRecoveryModule,
                                       &NumberRecoveryCapsules
                                       );

      DEBUG ((EFI_D_INFO | EFI_D_LOAD, "Number Of Recovery Capsules: %d\n", NumberRecoveryCapsules));

      if (NumberRecoveryCapsules == 0) {
        Index++;
      } else {
        break;
      }
    } else {
      ProviderAvailable = FALSE;
    }
  }

  //
  // If there is an image provider, get the capsule ID
  //
  if (ProviderAvailable) {
    RecoveryCapsuleSize = 0;

    Status = DeviceRecoveryModule->GetRecoveryCapsuleInfo (
                                    (EFI_PEI_SERVICES**)PeiServices,
                                    DeviceRecoveryModule,
                                    0,
                                    &RecoveryCapsuleSize,
                                    &DeviceId
                                    );

    if (EFI_ERROR (Status)) {
      return Status;
    }

    DEBUG ((EFI_D_INFO | EFI_D_LOAD, "Recovery Capsule Size: %d\n", RecoveryCapsuleSize));

    //
    // Only support the 2 capsule types known
    // Future enhancement is to rank-order the selection
    //
    if ((!CompareGuid (&DeviceId, &gRecoveryOnFatIdeDiskGuid)) &&
        (!CompareGuid (&DeviceId, &gRecoveryOnFatFloppyDiskGuid)) &&
        (!CompareGuid (&DeviceId, &gRecoveryOnDataCdGuid)) &&
       (!CompareGuid (&DeviceId, &gRecoveryOnFatUsbDiskGuid))
        ) {
      return EFI_UNSUPPORTED;
    }

    Buffer  = NULL;
    Status = (*PeiServices)->AllocatePages (
                               PeiServices,
                               EfiBootServicesCode,
                               (RecoveryCapsuleSize - 1) / 0x1000 + 1,
                               &Address
                               );

    DEBUG ((EFI_D_INFO | EFI_D_LOAD, "AllocatePage Returns: %r\n", Status));

    if (EFI_ERROR(Status)) {
      return Status;
    }

    Buffer = (UINT8 *) (UINTN) Address;

    (*PeiServices)->ReportStatusCode (
                      PeiServices,
                      EFI_PROGRESS_CODE,
                      EFI_SOFTWARE_PEI_MODULE | EFI_SW_PEIM_PC_CAPSULE_LOAD,
                      0,
                      NULL,
                      NULL
                      );

    Status = DeviceRecoveryModule->LoadRecoveryCapsule (
                                     (EFI_PEI_SERVICES**)PeiServices,
                                     DeviceRecoveryModule,
                                     0,
                                     Buffer
                                     );

    DEBUG ((EFI_D_INFO | EFI_D_LOAD, "LoadRecoveryCapsule Returns: %r\n", Status));

    if (EFI_ERROR (Status)) {
      return Status;
    }

    //
    // Update FV Hob if found
    //
    Status = (*PeiServices)->GetHobList (PeiServices, &Hob.Raw);
    HobOld.Raw  = Hob.Raw;
    while (!END_OF_HOB_LIST (Hob)) {
      if (Hob.Header->HobType == EFI_HOB_TYPE_FV) {
        DEBUG ((EFI_D_INFO | EFI_D_LOAD, "Hob FV Length: %x\n", Hob.FirmwareVolume->Length));
        //
        // BUGBUG Why is it a FV hob if it is greater than 0x50000?
        //
        if (Hob.FirmwareVolume->Length > 0x50000) {
          HobUpdate = TRUE;
          //
          // This looks like the Hob we are interested in
          //
          DEBUG ((EFI_D_INFO | EFI_D_LOAD, "Hob Updated\n"));
          Hob.FirmwareVolume->BaseAddress = (UINTN) Buffer;
          Hob.FirmwareVolume->Length      = RecoveryCapsuleSize;
        }
      }
      Hob.Raw = GET_NEXT_HOB (Hob);
    }

    FvHeader = (EFI_FIRMWARE_VOLUME_HEADER *)Buffer;
    CapsuleHeader = (OLD_EFI_CAPSULE_HEADER *)Buffer;

    //
    // Check if top of file is a capsule
    //
    if (CompareGuid ((EFI_GUID *)CapsuleHeader, &mEfiCapsuleHeaderGuid)) {
      FoundCapsule = TRUE;
    } else if (FvHeader->Signature == EFI_FVH_SIGNATURE) {
    	//
      // Assume the Firmware volume is a "FVMAIN" image
      //
      FoundFvMain = TRUE;
    }

    if (FoundFvMain) {
      //
      // build FV Hob if it is not built before
      //
      if (!HobUpdate) {
        DEBUG ((EFI_D_INFO | EFI_D_LOAD, "FV Hob is not found, Build FV Hob then..\n"));

       BuildFvHob (
         (UINTN)FvHeader,
         FvHeader->FvLength
          );
      }
    }

    if (FoundCapsule) {
      //
      // Build capsule hob
      //
      Status = (*PeiServices)->CreateHob (
                                 PeiServices,
                                 EFI_HOB_TYPE_CV,
                                 sizeof (EFI_HOB_CAPSULE_VOLUME),
                                 &CapsuleHob
                                 );
      if (EFI_ERROR (Status)) {
        return Status;
      }
      CapsuleHob->BaseAddress = (UINT64)((UINTN)CapsuleHeader + (UINTN)CapsuleHeader->OffsetToCapsuleBody);
      CapsuleHob->Length = (UINT64)((UINTN)CapsuleHeader->CapsuleImageSize -(UINTN)CapsuleHeader->OffsetToCapsuleBody);
      (*PeiServices)->ReportStatusCode (
                        PeiServices,
                        EFI_PROGRESS_CODE,
                        EFI_SOFTWARE_PEI_MODULE | EFI_SW_PEIM_PC_CAPSULE_START,
                        0,
                        NULL,
                        NULL
                        );
    }
  }
  DEBUG ((EFI_D_INFO | EFI_D_LOAD, "Recovery Module Returning: %r\n", Status));
  return Status;
}
